package com.gitee.sop.sopauth.auth.impl;

import com.alibaba.fastjson.JSONObject;
import com.gitee.sop.message.util.ObjectUtil;
import com.gitee.sop.mongodb.MongoDTO;
import com.gitee.sop.mongodb.MongoDbResult;
import com.gitee.sop.mongodb.MongoLike;
import com.gitee.sop.mongodb.MongoQuery;
import com.gitee.sop.servercommon.exception.ServiceException;
import com.gitee.sop.servercommon.util.IdUtils;
import com.gitee.sop.servercommon.util.ServletUtils;
import com.gitee.sop.servercommon.util.StringUtils;
import com.gitee.sop.servercommon.util.ip.IpUtils;
import com.gitee.sop.sopauth.auth.*;
import com.gitee.sop.sopauth.entity.UserInfo;
import com.gitee.sop.sopauth.enums.LoginType;
import com.gitee.sop.sopauth.enums.UserStatus;
import com.gitee.sop.sopauth.util.GenerateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.oltu.oauth2.as.issuer.MD5Generator;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * oauth2服务端默认实现
 *
 * @author admin
 */
@Service
@Slf4j
public class UserServiceImpl extends BaseServiceImpl implements OAuth2Service {

    private OAuthIssuer oauthIssuer = new OAuthIssuerImpl(new MD5Generator());

    @Autowired
    private OAuth2Manager oauth2Manager;

    @Autowired
    private AppIdManager appIdManager;

    @Override
    protected String getCollectionName() {
        return "portalUser";
    }

    @Override
    public FetchTokenResult authorize(FetchTokenParam authParam, OAuth2Config authConfig) {
        commonValidate(authParam);
        OpenUser user = login(authParam, true);
        return commonCreateAuthToken(user);
    }

    @Override
    public FetchTokenResult simpleLogin(FetchTokenParam authParam, OAuth2Config authConfig) {
        commonValidate(authParam);
        OpenUser user = login(authParam, false);
        return commonCreateAuthToken(user);
    }

    @Override
    public FetchTokenResult simpleRegister(FetchTokenParam authParam, OAuth2Config authConfig) {
        UserInfo userInfo = null;
        commonValidate(authParam);
        String loginType = authParam.getLoginType();
        if (LoginType.phone.getCode().equalsIgnoreCase(loginType) || LoginType.email.getCode().equalsIgnoreCase(loginType)) {
            String receiver = authParam.getUsername();
            if (StringUtils.isBlank(receiver)) {
                throw new ServiceException("账号不能为空");
            }
            //校验邮箱 手机号
            List<MongoQuery> mongoQueries = new ArrayList<>();
            mongoQueries.add(new MongoQuery("appId", authParam.getAppId()));
            mongoQueries.add(new MongoQuery("username", authParam.getUsername()));
            JSONObject jsonObject = mongoDbUtil.queryOne(getCollectionName(), mongoQueries, JSONObject.class);
            if (StringUtils.isNotNull(jsonObject)) {
                throw new ServiceException("对不起，您的账号：" + receiver + " 已存在，无需再注册");
            }
            jsonObject = register(authParam);
            jsonObject.put("loginIp", IpUtils.getIpAddr(ServletUtils.getRequest()));
            jsonObject.put("loginTime", new Date());
            mongoDbUtil.updateOne(getCollectionName(), "uuid", jsonObject.getString("uuid"), jsonObject.getInnerMap());
            userInfo = new UserInfo();
            ObjectUtil.switchAllFieldValue(userInfo, jsonObject);
        } else if (LoginType.pwd.getCode().equalsIgnoreCase(loginType)) {
            //账号密码注册
            //
            String username = authParam.getUsername();
            String password = authParam.getPassword();
            if (StringUtils.isBlank(password)) {
                //设置一个默认密码 或者 提示密码未设置
                throw new ServiceException("密码不能为空");
            }
            String userPassword = GenerateUtil.getUserPassword(username, password);
            authParam.setPassword(userPassword);
            JSONObject jsonObject = register(authParam);
            jsonObject.put("loginIp", IpUtils.getIpAddr(ServletUtils.getRequest()));
            jsonObject.put("loginTime", new Date());
            mongoDbUtil.updateOne(getCollectionName(), "uuid", jsonObject.getString("uuid"), jsonObject.getInnerMap());
            userInfo = new UserInfo();
            ObjectUtil.switchAllFieldValue(userInfo, jsonObject);
        }
        if (StringUtils.isNull(userInfo)) {
            throw new ServiceException("注册失败");
        }
        return commonCreateAuthToken(userInfo);
    }


    @Override
    public OpenUser getInfo(String accessToken) {
        return oauth2Manager.getOpenUserByAccessToken(accessToken);
    }

    @Override
    public MongoDbResult queryPageByParam(MongoDTO mongoDTO) {
        Set<MongoLike> queryParams = mongoDTO.getQueryParams();
        Map<String, String> sortParams = mongoDTO.getSortParams();
        int current = mongoDTO.getCurrent();
        int size = mongoDTO.getSize();
        MongoDbResult mongoDbResult = mongoDbUtil.queryByParameterLike(getCollectionName(), queryParams, sortParams, current, size, JSONObject.class);
        return mongoDbResult;
    }

    @Override
    public JSONObject searchOne(String userUuid) {
        List<MongoQuery> mongoQueries = new ArrayList<>();
        mongoQueries.add(new MongoQuery("uuid", userUuid));
        JSONObject jsonObject = mongoDbUtil.queryOne(getCollectionName(), mongoQueries, JSONObject.class);
        return jsonObject;
    }

    @Override
    public void updateOne(String userUuid, String status) {
        Map<String, Object> updateMap = new HashMap<>();
        updateMap.put("status", status);
        mongoDbUtil.updateOne(getCollectionName(), "uuid", userUuid, updateMap);
    }

    @Override
    public void deleteOne(String userUuid) {
        List<Object> ids = new ArrayList<>();
        ids.add(userUuid);
        mongoDbUtil.deleteDocument(getCollectionName(), "uuid", ids);
    }


    /**
     * 统一校验参数
     *
     * @param authParam 参数
     */
    private void commonValidate(FetchTokenParam authParam) {
        // 构建OAuth 授权请求
        String clientId = authParam.getAppId();
        // 检查传入的客户端id是否正确
        if (!appIdManager.isValidAppId(clientId)) {
            throw new ServiceException("客户端校验出错");
        }
    }

    /**
     * 创建token
     *
     * @param user 用户信息
     * @return
     */
    private FetchTokenResult commonCreateAuthToken(OpenUser user) {
        try {
            Token token = oauth2Manager.getTokenByAuthCode(user.getUserId());
            if (StringUtils.isNull(token)) {
                // 生成授权码
                TokenPair tokenPair = this.createNewToken(user);
                oauth2Manager.addToken(user.getUserId(), tokenPair.getAccessToken(), tokenPair.getRefreshToken(), user);
                oauth2Manager.addAccessToken(tokenPair.getAccessToken(), tokenPair.getRefreshToken(), user);
                return this.buildTokenResult(tokenPair, user);
                // 构建响应
            } else {
                // 老的token对
                TokenPair oldTokenPair = new TokenPair();
                oldTokenPair.setAccessToken(token.getAccessToken());
                oldTokenPair.setRefreshToken(token.getRefreshToken());
                // 创建一对新的accessToken和refreshToken
                TokenPair newTokenPair = this.createRefreshToken(user, oldTokenPair);
                this.afterRefreshToken(oldTokenPair, newTokenPair, user);
                oauth2Manager.addToken(user.getUserId(), newTokenPair.getAccessToken(), newTokenPair.getRefreshToken(), user);
                // 返回新的accessToken和refreshToken
                return this.buildTokenResult(newTokenPair, user);
            }
        } catch (Exception e) {
            // 返回错误消息（如?error=）
            throw new ServiceException("获取授权码失败");
        }
    }

    /**
     * 登录
     *
     * @param tokenParam   参数
     * @param openRegister 是否开启静默注册，不开启时返回先注册
     * @return
     */
    private OpenUser login(FetchTokenParam tokenParam, boolean openRegister) {
        // 这里应该先检查用户有没有登录，如果登录直接返回openUser
        UserInfo userInfo = new UserInfo();
        String loginType = tokenParam.getLoginType();
        if (StringUtils.isBlank(loginType)) {
            throw new ServiceException("登录类型错误");
        }
        if (LoginType.phone.getCode().equalsIgnoreCase(loginType) || LoginType.email.getCode().equalsIgnoreCase(loginType)) {
            String receiver = tokenParam.getUsername();
            if (StringUtils.isBlank(receiver)) {
                throw new ServiceException("登录用户账号不能为空");
            }
            //校验邮箱 手机号
            List<MongoQuery> mongoQueries = new ArrayList<>();
            mongoQueries.add(new MongoQuery("appId", tokenParam.getAppId()));
            mongoQueries.add(new MongoQuery("username", tokenParam.getUsername()));
            JSONObject jsonObject = mongoDbUtil.queryOne(getCollectionName(), mongoQueries, JSONObject.class);
            if (StringUtils.isNull(jsonObject)) {
                if (openRegister) {
                    //静默注册
                    jsonObject = register(tokenParam);
                } else {
                    throw new ServiceException("对不起，您的账号：" + receiver + " 不存在，请先注册");
                }
            }
            String status = jsonObject.getString("status");
            if (UserStatus.DELETED.getCode().equals(status)) {
                throw new ServiceException("对不起，您的账号：" + receiver + " 已被删除");
            }
            if (UserStatus.DISABLE.getCode().equals(status)) {
                throw new ServiceException("对不起，您的账号：" + receiver + " 已停用");
            }

            jsonObject.put("loginIp", IpUtils.getIpAddr(ServletUtils.getRequest()));
            jsonObject.put("loginTime", new Date());
            mongoDbUtil.updateOne(getCollectionName(), "uuid", jsonObject.getString("uuid"), jsonObject.getInnerMap());
            ObjectUtil.switchAllFieldValue(userInfo, jsonObject);
        } else if (LoginType.pwd.getCode().equalsIgnoreCase(loginType)) {
            //获取账号与密码
            String username = tokenParam.getUsername();
            String password = tokenParam.getPassword();
            //验证密码是否正确
            String userPassword = GenerateUtil.getUserPassword(username, password);
            //与数据库中的加密密码进行匹配
            //校验邮箱 手机号
            List<MongoQuery> mongoQueries = new ArrayList<>();
            mongoQueries.add(new MongoQuery("appId", tokenParam.getAppId()));
            mongoQueries.add(new MongoQuery("username", tokenParam.getUsername()));
            mongoQueries.add(new MongoQuery("password", userPassword));

            JSONObject jsonObject = mongoDbUtil.queryOne(getCollectionName(), mongoQueries, JSONObject.class);
            if (Objects.nonNull(jsonObject)) {
                //用户登录成功
                String status = jsonObject.getString("status");
                if (UserStatus.DELETED.getCode().equals(status)) {
                    throw new ServiceException("对不起，您的账号：" + tokenParam.getUsername() + " 已被删除");
                }
                if (UserStatus.DISABLE.getCode().equals(status)) {
                    throw new ServiceException("对不起，您的账号：" + tokenParam.getUsername() + " 已停用");
                }
                jsonObject.put("loginIp", IpUtils.getIpAddr(ServletUtils.getRequest()));
                jsonObject.put("loginTime", new Date());
                mongoDbUtil.updateOne(getCollectionName(), "uuid", jsonObject.getString("uuid"), jsonObject.getInnerMap());
                ObjectUtil.switchAllFieldValue(userInfo, jsonObject);
            } else {
                throw new ServiceException("登录失败");
            }
        }
        return userInfo;
    }

    /**
     * 注册
     *
     * @param tokenParam 参数
     * @return
     */
    private JSONObject register(FetchTokenParam tokenParam) {
        JSONObject object = new JSONObject();
        object.put("appId", tokenParam.getAppId());
        object.put("appName", tokenParam.getAppName());
        object.put("uuid", IdUtils.fastSimpleUUID());
        object.put("username", tokenParam.getUsername());
        object.put("password", tokenParam.getPassword());
        object.put("registerTime", new Date());
        object.put("registerIp", IpUtils.getIpAddr(ServletUtils.getRequest()));
        object.put("status", UserStatus.OK.getCode());
        object.put("loginTime", null);
        object.put("loginIp", null);
        JSONObject extData = tokenParam.getExtData();
        if (StringUtils.isNotNull(extData)) {
            object.putAll(extData);
        }
        List<JSONObject> userExts = new ArrayList<>();
        userExts.add(object);
        mongoDbUtil.insertDocument(getCollectionName(), userExts);
        return object;
    }


    /**
     * 刷新token后续操作
     *
     * @param oldTokenPair 老的token
     * @param newTokenPair 新的token
     * @param user         用户
     */
    protected void afterRefreshToken(TokenPair oldTokenPair, TokenPair newTokenPair, OpenUser user) {
        // 保存token
        oauth2Manager.addAccessToken(newTokenPair.getAccessToken(), newTokenPair.getRefreshToken(), user);

        // 删除老的accessToken
        oauth2Manager.removeAccessToken(oldTokenPair.getAccessToken());
    }

    /**
     * 创建新的token
     *
     * @param user
     * @return 返回新token
     */
    protected TokenPair createNewToken(OpenUser user) {
        return this.createDefaultTokenPair();
    }

    /**
     * 返回刷新后token
     *
     * @param user         用户
     * @param oldTokenPair 旧的token
     * @return 返回新的token
     */
    protected TokenPair createRefreshToken(OpenUser user, TokenPair oldTokenPair) {
        return this.createDefaultTokenPair();
    }


    private TokenPair createDefaultTokenPair() {
        TokenPair tokenPair = new TokenPair();
        try {
            String accessToken = oauthIssuer.accessToken();
            String refreshToken = oauthIssuer.refreshToken();

            tokenPair.setAccessToken(accessToken);
            tokenPair.setRefreshToken(refreshToken);

            return tokenPair;
        } catch (OAuthSystemException e) {
            throw new RuntimeException(e);
        }
    }

    private FetchTokenResult buildTokenResult(TokenPair tokenPair, OpenUser user) {
        OAuth2Config auth2Config = OAuth2Config.getInstance();
        FetchTokenResult fetchTokenResult = new FetchTokenResult();
        fetchTokenResult.setApp_auth_token(tokenPair.getAccessToken());
        fetchTokenResult.setApp_refresh_token(tokenPair.getRefreshToken());
        fetchTokenResult.setUser_id(user.getUserId());
        fetchTokenResult.setExpires_in(auth2Config.getAccessTokenExpiresIn());
        fetchTokenResult.setRe_expires_in(auth2Config.getRefreshTokenExpiresIn());
        return fetchTokenResult;
    }

}
